package org.demo.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang.StringUtils;
import org.demo.dao.model.Article;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import com.google.common.collect.Lists;

public class ElasticSearchUtil {
	
	public ElasticSearchUtil() {}
	
	public ElasticSearchUtil(String index, String type, String id) {
		this.index = index;
		this.type = type;
		this.id = id;
	}
	
	public ElasticSearchUtil(String index, String type) {
		this.index = index;
		this.type = type;
	}

	@Autowired
	private ThreadPoolTaskExecutor threadPoolTaskExecutor;
	
	private String index;
	private String type;
	private String id;
	
	public static TransportClient client = null;
	private static final String ES_HOST = "119.23.13.226";
	private static final Integer ES_PORT = 9300;
	private static final SimpleDateFormat SDF_UTC = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
	
	public static Client getClient() {
		try {
			if(client == null) {
				client = new PreBuiltTransportClient(Settings.EMPTY)
				        .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ES_HOST),ES_PORT));
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		return client;
	}
	
	public static void close() {
		client.close();
	}
	
	public static HighlightBuilder getHighlight(String... parms) {
		HighlightBuilder hBuilder = null;
		if(parms != null || parms.length != 0) {
			hBuilder = new HighlightBuilder();
			for(String parm:parms) {
				hBuilder.field(parm);
				
			}
	        hBuilder.preTags("<span style=\"color:red\">");
	        hBuilder.postTags("</span>");
		}
		
        return hBuilder;
	}
	
	/**
	 * 添加索引
	 * @param index	索引
	 * @param type	类型
	 * @param id	ID
	 * @param clazz	数据类型
	 * @param object	实体
	 */
	public void addIndex(Class<?> clazz,Object object) {
		Field[] fields = clazz.getDeclaredFields();
		
		threadPoolTaskExecutor.execute(new Runnable() {

			@Override
			public void run() {
				try {
					XContentBuilder builder = XContentFactory.jsonBuilder()
							.startObject();
					for(Field field:fields) {
						if("serialVersionUID".equals(field.getName())) {
							continue;
						}
						String fieldName = field.getName();
						Method method = clazz.getDeclaredMethod("get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1), null);
						builder.field(fieldName, method.invoke(object, null));
					}
					builder.endObject();
					ElasticSearchUtil.getClient().prepareIndex(index, type, id).setSource(builder)
							.get();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	/**
	 * 搜索
	 * @param index	索引
	 * @param type	类型
	 * @param search	关键词
	 * @param pageNo	页码
	 * @param pageSize	每页显示项数
	 * @param showHigh	是否高亮
	 * @param clazz	数据类型
	 * @param searchField	搜索字段
	 * @return
	 */
	public Page getListBySearch(String search, Integer pageNo, Integer pageSize,
				Boolean showHigh, Class<?> clazz, String... searchField){
		pageSize = pageSize == null ? 5 : pageSize;
		pageNo = pageNo == null ? 0 : pageNo;
		List<Object> list = Lists.newArrayList();
		MultiMatchQueryBuilder queryBuilder = null;
		Page page = new Page();
		try {
			Class<?> forName = Class.forName(clazz.getName());
			if (StringUtils.isNotBlank(search)) {
				queryBuilder = QueryBuilders.multiMatchQuery(search, searchField);

			}
			SearchResponse searchResponse = ElasticSearchUtil.getClient()
					.prepareSearch(index)
					.setTypes(type)
					.setQuery(queryBuilder)
					.setFrom(pageNo)
					.setSize(pageSize)
					.highlighter(showHigh ? ElasticSearchUtil.getHighlight(searchField) : null)
					.execute()
					.actionGet();
			page.setTotalPage(searchResponse.getHits().getTotalHits() % pageSize == 0 ? searchResponse.getHits().getTotalHits() / pageSize : searchResponse.getHits().getTotalHits() / pageSize + 1);
			page.setCount(searchResponse.getHits().getTotalHits());
			for (SearchHit hit : searchResponse.getHits()) {
				Object newInstance = forName.newInstance();
				Map<String, Object> source = hit.getSource();
				if(showHigh) {
					Map<String, HighlightField> result = hit.highlightFields();
					for(Entry<String, Object> entry:source.entrySet()) {
						Class<?> fieldType = clazz.getDeclaredField(entry.getKey()).getType();
						Method method = forName.getDeclaredMethod(
								"set"+entry.getKey().substring(0,1).toUpperCase()+entry.getKey().substring(1), fieldType);
						HighlightField highField = result.get(entry.getKey());
						if (highField !=null) {
							String html = "";
		                    // 取得定义的高亮标签
		                    Text[] titleTexts = highField.fragments();
		                    // 为title串值增加自定义的高亮标签
		                    for (Text text : titleTexts) {
		                    	html += text;
		                    }
		                    method.invoke(newInstance, html);
		                }else {
		                	if(fieldType.getSimpleName().equalsIgnoreCase("Date")) {
		                		method.invoke(newInstance, ElasticSearchUtil.SDF_UTC.parse(entry.getValue()+""));
		                	}else {
		                		method.invoke(newInstance, entry.getValue());
		                	}
		                }
					};
				}else {
					for(Entry<String, Object> entry:source.entrySet()) {
						Class<?> fieldType = clazz.getDeclaredField(entry.getKey()).getType();
						Method method = forName.getDeclaredMethod(
								"set"+entry.getKey().substring(0,1).toUpperCase()+entry.getKey().substring(1), fieldType);
						if(fieldType.getSimpleName().equalsIgnoreCase("Date")) {
	                		method.invoke(newInstance, ElasticSearchUtil.SDF_UTC.parse(entry.getValue()+""));
	                	}else {
	                		method.invoke(newInstance, entry.getValue());
	                	}
					}
				}
				list.add(newInstance);
				newInstance = null;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
//			ElasticSearchUtil.close();
		}
		page.setCurrentPage(pageNo);
		page.setData(list);
		return page;
	}
	
	public Object getDetails(Class<?> clazz) {
		Object newInstance = null;
		SearchResponse response = client.prepareSearch(index)  
				.setTypes(type).setQuery(QueryBuilders.matchQuery("id", id)).execute()  
				.actionGet();
		if(response.getHits().totalHits == 1) {
			SearchHit at = response.getHits().getAt(0);
			Map<String, Object> source = at.getSource();
			try {
				Class<?> forName = Class.forName(clazz.getName());
				newInstance = forName.newInstance();
				for(Entry<String, Object> entry:source.entrySet()) {
					Class<?> fieldType = clazz.getDeclaredField(entry.getKey()).getType();
					Method method = forName.getDeclaredMethod(
							"set"+entry.getKey().substring(0,1).toUpperCase()+entry.getKey().substring(1), fieldType);
					if(fieldType.getSimpleName().equalsIgnoreCase("Date")) {
		        		method.invoke(newInstance, ElasticSearchUtil.SDF_UTC.parse(entry.getValue()+""));
		        	}else {
		        		method.invoke(newInstance, entry.getValue());
		        	}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return newInstance;
	}
	
}
